import java.util.ArrayList;
import java.io.File;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;

public class Profile {

	/*
	 * 
	 *    GAME SAVING
	 * 
	 */

	/*
	 * Save a pet
	 * String elemName is either "active" or "passive" depending
	 * on which pet is saved.
	 */
	private static void createPetElement(String elemName, Pet pet, Document doc, Element rootElement) {
		if (pet == null) {
			Element petElement = doc.createElement(elemName);
			rootElement.appendChild(petElement);
		} else {
			Element petElement = doc.createElement(elemName);
			rootElement.appendChild(petElement);
			petElement.setAttribute("kind", pet.getClass().getSimpleName());
			Element petName = doc.createElement("name");
			petName.appendChild(doc.createTextNode(pet.getName()));
			petElement.appendChild(petName);
			Element petXP = doc.createElement("xp");
			petXP.appendChild(doc.createTextNode(Integer.toString(pet.getCurrentXP())));
			petElement.appendChild(petXP);
			Element petDmg = doc.createElement("damage");
			petDmg.appendChild(doc.createTextNode(Integer.toString(pet.getDamageTaken())));
			petElement.appendChild(petDmg);
			Element petDef = doc.createElement("defense");
			petDef.appendChild(doc.createTextNode(Integer.toString(pet.getDefense())));
			petElement.appendChild(petDef);
			Element petStr = doc.createElement("strength");
			petStr.appendChild(doc.createTextNode(Integer.toString(pet.getStrength())));
			petElement.appendChild(petStr);
			Element petSpd = doc.createElement("speed");
			petSpd.appendChild(doc.createTextNode(Integer.toString(pet.getSpeed())));
			petElement.appendChild(petSpd);
			Element petSize = doc.createElement("size");
			petSize.appendChild(doc.createTextNode(Integer.toString(pet.getSize())));
			petElement.appendChild(petSize);
			
			ArrayList<AttackName> petAttacks = pet.getAttacks();
			
			for (AttackName attack : petAttacks) {
				Element attackElement = doc.createElement("attack");
				attackElement.setAttribute("name", attack.name());
				petElement.appendChild(attackElement);
			}
		}
	}
	
	public static void createSpellBookElement( Player player, Document doc, Element rootElement){
		Element sbElement = doc.createElement("spellBook");
		rootElement.appendChild(sbElement);
		for (AttackName attack : player.spellBook.getAttacks()) {
			Element attackElement = doc.createElement("attack");
			attackElement.setAttribute("kind", attack.name());
			sbElement.appendChild(attackElement);
		}
	}

	/*
	 * Save the inventory
	 */
	private static void createInventoryElement(Inventory inv, Document doc, Element rootElement) {
		Element invElement = doc.createElement("inventory");
		rootElement.appendChild(invElement);
		
		for (ItemKind item : inv.getItems()) {
			Element itemElement = doc.createElement("item");
			itemElement.setAttribute("kind", item.name());
			invElement.appendChild(itemElement);
			itemElement.appendChild(doc.createTextNode(Integer.toString(inv.itemCount(item))));
		}
	}
	
	private static void createQuestList(String tagname, ArrayList<Integer> questlist, Document doc, Element rootElement) {
		Element fqElement = doc.createElement(tagname);
		rootElement.appendChild(fqElement);
		
		for (int qi : questlist) {
			Element qiElement = doc.createElement("id");
			fqElement.appendChild(qiElement);
			qiElement.appendChild(doc.createTextNode(Integer.toString(qi)));
		}
	}

	private static void createDefeatedTrainerList(ArrayList<Integer> dtlist, Document doc, Element rootElement) {
		Element dtElement = doc.createElement("defeated");
		rootElement.appendChild(dtElement);
		
		for (int ti : dtlist) {
			Element tiElement = doc.createElement("id");
			dtElement.appendChild(tiElement);
			tiElement.appendChild(doc.createTextNode(Integer.toString(ti)));
		}
	}
	
	public static void save(Player player, String filename){
		try {
			/*
			 * Create XML document
			 */
			DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
			DocumentBuilder docBuilder = docFactory.newDocumentBuilder();
			Document doc = docBuilder.newDocument();
			Element rootElement = doc.createElement("savegame");
			doc.appendChild(rootElement);
			/*
			 * Save personal details
			 */
			Element playerNameElement = doc.createElement("name");
			playerNameElement.appendChild(doc.createTextNode(player.playerName));
			rootElement.appendChild(playerNameElement);
			Element playerDifficultyElement = doc.createElement("difficulty");
			playerDifficultyElement.appendChild(doc.createTextNode(player.difficulty.name()));
			rootElement.appendChild(playerDifficultyElement);
			Element playerMoneyElement = doc.createElement("money");
			playerMoneyElement.appendChild(doc.createTextNode(Integer.toString(player.money)));
			rootElement.appendChild(playerMoneyElement);
			Element playerSeedElement = doc.createElement("seed");
			playerSeedElement.appendChild(doc.createTextNode(Integer.toString(player.seedN)));
			rootElement.appendChild(playerSeedElement);
			Element playerLocationElement = doc.createElement("location");
			playerLocationElement.appendChild(doc.createTextNode(Integer.toString(player.nodeIndex)));
			rootElement.appendChild(playerLocationElement);


			/*
			 * Save pets and then inventory
			 */
			createPetElement("active", player.activePet, doc, rootElement);
			createPetElement("passive", player.passivePet, doc, rootElement);
			
			createInventoryElement(player.inventory, doc, rootElement);
			
			createSpellBookElement(player, doc, rootElement);
			createDefeatedTrainerList(player.defeatedTrainers, doc, rootElement);
			createQuestList("activequests", player.activeQuests, doc, rootElement);
			createQuestList("finishedquests", player.finishedQuests, doc, rootElement);
			
			/*
			 * Write to file!
			 */
			TransformerFactory transformerFactory = TransformerFactory.newInstance();
			Transformer transformer = transformerFactory.newTransformer();
			DOMSource source = new DOMSource(doc);
			StreamResult result = new StreamResult(new File(filename));
			transformer.transform(source, result);
		} catch (ParserConfigurationException pce) {
			pce.printStackTrace();
		} catch (TransformerException tfe) {
			tfe.printStackTrace();
		}
	}
	
	
	
	
	
	
	/*
	 * 
	 *    GAME LOADING
	 * 
	 */
	
	public static Pet loadPetElement(String tagName, Document doc, Element rootElement) throws ClassNotFoundException, NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
		try {
			Element petElement = (Element) rootElement.getElementsByTagName(tagName).item(0); 
			
			String petKind = petElement.getAttribute("kind");
			String petName = petElement.getElementsByTagName("name").item(0).getTextContent();
			int petXP = Integer.parseInt(petElement.getElementsByTagName("xp").item(0).getTextContent());
			int petDmg = Integer.parseInt(petElement.getElementsByTagName("damage").item(0).getTextContent());
			int petDef = Integer.parseInt(petElement.getElementsByTagName("defense").item(0).getTextContent());
			int petStrength = Integer.parseInt(petElement.getElementsByTagName("strength").item(0).getTextContent());
			int petSpd = Integer.parseInt(petElement.getElementsByTagName("speed").item(0).getTextContent());
			int petSize = Integer.parseInt(petElement.getElementsByTagName("size").item(0).getTextContent());
	
			/*
			 * Ladda attacker! (loop is the shit?)
			 */	
			ArrayList<AttackName> attacks = new ArrayList<AttackName>();
			NodeList attackElements = petElement.getElementsByTagName("attack");
			for (int i = 0; i < attackElements.getLength(); i++) {
				Element attack = (Element) attackElements.item(i);
				String attackName = attack.getAttribute("name");
				attacks.add(AttackName.valueOf(attackName)); 
			}
			
			/*
			 * skapa pet!
			 */
			Class clazz = Class.forName(petKind);
			Constructor tc = clazz.getConstructor(String.class, int.class, int.class, int.class, int.class, int.class, int.class, ArrayList.class);
			Pet loadedPet = (Pet) tc.newInstance(petName, petXP, petDmg, petDef, petStrength, petSpd, petSize, attacks);
			return loadedPet;
		} catch (NullPointerException e) {
			return null;
		}
	}

	/*
	 * Ladda föremål!
	 */
	public static ArrayList<ItemKind> loadInventory(Document doc, Element rootElement) throws ClassNotFoundException, NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
		Element invElement = (Element) rootElement.getElementsByTagName("inventory").item(0);
		
		ArrayList<ItemKind> items = new ArrayList<ItemKind>();
		NodeList itemElements = invElement.getElementsByTagName("item");
		for (int i = 0; i < itemElements.getLength(); i++) {
			Element item = (Element) itemElements.item(i);
			String itemKind = item.getAttribute("kind");
			int amount = Integer.parseInt(item.getTextContent());
			for (int j = 0; j < amount; j++) {
				items.add(ItemKind.valueOf(itemKind));
			}
		}
		
		return items;
	}
	
	public static ArrayList<AttackName> loadSpellBook(Document doc, Element rootElement) throws ClassNotFoundException, NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
		Element sbElement = (Element) rootElement.getElementsByTagName("spellBook").item(0);
		
		ArrayList<AttackName> items = new ArrayList<AttackName>();
		NodeList attackElements = sbElement.getElementsByTagName("attack");
		for (int i = 0; i < attackElements.getLength(); i++) {
			Element item = (Element) attackElements.item(i);
			String attackName = item.getAttribute("kind");
			items.add(AttackName.valueOf(attackName));
		}
		
		return items;
	}
	
	public static ArrayList<Integer> loadQuestList(String tagname, Document doc, Element rootElement) throws ClassNotFoundException, NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
		Element fqElement = (Element) rootElement.getElementsByTagName(tagname).item(0);
		
		ArrayList<Integer> quests = new ArrayList<Integer>();
		NodeList qiElements = fqElement.getElementsByTagName("id");
		for (int i = 0; i < qiElements.getLength(); i++) {
			Element qi = (Element) qiElements.item(i);
			quests.add(Integer.parseInt(qi.getTextContent()));
		}
		
		return quests;
	}
	
	public static ArrayList<Integer> loadDefeatedTrainersList(Document doc, Element rootElement) throws ClassNotFoundException, NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
		Element dtElement = (Element) rootElement.getElementsByTagName("defeated").item(0);
		
		ArrayList<Integer> dtlist = new ArrayList<Integer>();
		NodeList tiElements = dtElement.getElementsByTagName("id");
		for (int i = 0; i < tiElements.getLength(); i++) {
			Element ti = (Element) tiElements.item(i);
			dtlist.add(Integer.parseInt(ti.getTextContent()));
		}
		
		return dtlist;
	}
	
	public static Player load(String filename){
		/*
		 * [X] name
		 * [X] difficulty
		 * [X] money
		 * [X] activepet
		 * [X] passivepet
		 * [X] inventory
		 */
		Player loadedPlayer = null;
		try {
			File savefile = new File(filename);
			DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
			DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
			Document doc = dBuilder.parse(savefile);
			doc.getDocumentElement().normalize();
		
			Element rootElement = (Element) doc.getElementsByTagName("savegame").item(0);
			String playerName = rootElement.getElementsByTagName("name").item(0).getTextContent();
			Difficulty difficulty = Difficulty.valueOf(rootElement.getElementsByTagName("difficulty").item(0).getTextContent());
			int money = Integer.parseInt(rootElement.getElementsByTagName("money").item(0).getTextContent());
			int seed = Integer.parseInt(rootElement.getElementsByTagName("seed").item(0).getTextContent());
			int nodeIndex = Integer.parseInt(rootElement.getElementsByTagName("location").item(0).getTextContent());
			Pet activePet = loadPetElement("active", doc, rootElement);
			Pet passivePet = loadPetElement("passive", doc, rootElement);
			
			loadedPlayer = new Player(playerName, money, seed, activePet, passivePet, difficulty, nodeIndex);
			loadedPlayer.passivePet = passivePet;
			
			ArrayList<ItemKind> items = loadInventory(doc, rootElement);
			for (ItemKind item : items) {
				loadedPlayer.inventory.addItem(item, 1);
			}
			loadedPlayer.defeatedTrainers.addAll(loadDefeatedTrainersList(doc, rootElement));
			loadedPlayer.activeQuests.addAll(loadQuestList("activequests", doc, rootElement));
			loadedPlayer.finishedQuests.addAll(loadQuestList("finishedquests", doc, rootElement));
			ArrayList<AttackName> sb = loadSpellBook(doc, rootElement);
			for (AttackName sbtemp : sb) {
				loadedPlayer.spellBook.addItem(sbtemp);
			}
			
		} catch (Exception e) {
			e.printStackTrace();
			System.out.println("============ Error in the save file, generating new game ============ ");
			loadedPlayer = new Player("PLAYER_NAME", 100, 0, new Baconator("BACONATOR_NAME", 203), null, Difficulty.Sandbox, 0);
			try {
				save(loadedPlayer, filename);
			} catch (Exception f) {
				f.printStackTrace();
				System.out.println("Couldn't save new game, going with the flow");
			}
	    }
		
		return loadedPlayer;
	}
	
	
}
